/*
 * Copyright 2015-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER")

package com.willowtreeapps.opentest4k

import kotlin.reflect.KClass

/**
 * Serializable representation of a value that was used in an assertion.
 *
 * This class only stores the value if it implements
 * [Serializable][java.io.Serializable] (on js this is always the case).
 *
 * In any case, it stores its runtime type, [identity hash code][identityHashCode]},
 * and [string representation][stringRepresentation]} determined via `toString()`.
 * If the invocation of `toString()` throws an [Exception], the string representation
 * will take the form of `"<Exception in toString(): " + e + ">"`, where "e" is the
 * caught exception.
 *
 * The [toString] method returns the string representation of the
 * value along with its type and identity hash code.
 *
 * @author Marc Philipp
 * @author Sam Brannen
 * @since 1.0
 */
expect class ValueWrapper

/**
 * Factory for creating a new `ValueWrapper` for the supplied `value`.
 *
 *
 * If the supplied `value` is `null`, this method will return a
 * cached `ValueWrapper` suitable for all `null` values.
 * If the supplied `value` is already an instance of [ValueWrapper],
 * it will be returned as is.
 *
 * @receiver value the value to wrap; may be `null`
 * @return a wrapper for the supplied value; never `null`
 */
expect fun Any?.toValueWrapper(): ValueWrapper

/**
 * Factory for creating a new `ValueWrapper` for the supplied `value`
 * using the supplied custom `stringRepresentation`.
 *
 *
 * You should use this method when you don't want to rely on the result of the
 * value's [toString()][Any.toString] method.
 *
 *
 * If the supplied `value` is `null`, this method will return a
 * cached `ValueWrapper` suitable for all `null` values.
 * If the supplied `value` is already an instance of [ValueWrapper],
 * it will be returned as is if the `stringRepresentation` match, otherwise
 * the original value will be unwrapped and a new `ValueWrapper` with the
 * new `stringRepresentation` will be created.
 *
 * @receiver value the value to wrap; may be `null`
 * @param stringRepresentation a custom rendering of the value; will fallback to
 * the default behavior if `null`
 * @return a wrapper for the supplied value; never `null`
 * @since 1.2
 */
expect fun Any?.toValueWrapper(stringRepresentation: String?): ValueWrapper

/**
 * Returns the value supplied to [toValueWrapper] if the value
 * [Serializable][java.io.Serializable] (always true on js and native); otherwise, `null`.
 */
expect val ValueWrapper.value: Any?

/**
 * Returns the value's runtime type or `null` if the value is
 * `null`.
 */
expect val ValueWrapper.valueType: KClass<*>?

/**
 * Returns the value's string representation.
 *
 * The string representation is generated by invoking `String.valueOf(value)` ([toString] on js and native) for the value
 * supplied to [toValueWrapper].
 *
 * @see value
 */
expect val ValueWrapper.stringRepresentation: String

/**
 * Returns the value's identity hash code.
 *
 * The identity hash code is generated by invoking
 * `System.identityHashCode` ([hashCode] on js and native)
 * for the value supplied to [toValueWrapper].
 */
expect val ValueWrapper.identityHashCode: Int

/**
 * Returns the original value supplied to [toValueWrapper].
 *
 * If this `ValueWrapper` was created by deserialization this method
 * returns `null`.
 *
 * @see value
 * @since 1.2
 */
expect val ValueWrapper.ephemeralValue: Any?

/**
 * `AssertionFailedError` is a common base class for test-related
 * [AssertionErrors][AssertionError].
 *
 * In addition to a message and a cause, this class stores the expected
 * and actual values of an assertion using the [ValueWrapper] type.
 *
 * @author Sam Brannen
 * @author Marc Philipp
 * @since 1.0
 */
expect open class AssertionFailedError : AssertionError {
    /**
     * Constructs an [AssertionFailedError] with an empty message,
     * no cause, and no expected/actual values.
     */
    constructor()
    /**
     * Constructs an [AssertionFailedError] with a message, no cause,
     * and no expected/actual values.
     *
     * @param message the detail message; `null` or blank will be
     * converted to the empty [String]
     */
    constructor(message: String?)
    /**
     * Constructs an [AssertionFailedError] with a message and a cause
	 * but without expected/actual values.
     *
     * @param message the detail message; `null` or blank will be
     * converted to the empty [String]
     * @param cause the cause of the failure
     */
    constructor(message: String?, cause: Throwable?)
    /**
     * Constructs an [AssertionFailedError] with a message and
	 * expected/actual values but without a cause.
     *
     * @param message the detail message; `null` or blank will be
     * converted to the empty [String]
     * @param expected the expected value; may be `null`
     * @param actual the actual value; may be `null`
     */
    constructor(message: String?, expected: Any?, actual: Any?)
    /**
     * Constructs an [AssertionFailedError] with a message,
	 * expected/actual values, and a cause.
     *
     * @param message the detail message; `null` or blank will be
     * converted to the empty [String]
     * @param expected the expected value; may be `null`
     * @param actual the actual value; may be `null`
     * @param cause the cause of the failure
     */
    constructor(message: String?, expected: Any?, actual: Any?, cause: Throwable?)
}

/**
 * Returns `true` if an *expected value* was supplied via an
 * appropriate constructor.
 *
 * @see [expected]
 */
expect val AssertionFailedError.isExpectedDefined: Boolean

/**
 * Returns `true` if an *actual value* was supplied via an
 * appropriate constructor.
 *
 * @see [actual]
 */
expect val AssertionFailedError.isActualDefined: Boolean

/**
 * Returns the wrapped expected value if it is defined; otherwise `null`.
 *
 * @see [isExpectedDefined]
 */
expect val AssertionFailedError.expected: ValueWrapper?

/**
 * Returns the wrapped actual value if it is defined; otherwise `null`.
 *
 * @see [isActualDefined]
 */
expect val AssertionFailedError.actual: ValueWrapper?

/**
 * `MultipleFailuresError` is an [AssertionError] that aggregates
 * multiple failures thrown in a given context (i.e., typically within the
 * invocation of a single test).
 *
 * @author Johannes Link
 * @author Sam Brannen
 * @author Marc Philipp
 * @since 1.0
 */
expect open class MultipleFailuresError
    /**
     * Constructs a [MultipleFailuresError] with the supplied heading and
     * failures.
     *
     * @param heading the message heading; a default value will be used if
     * `null` or blank
     * @param failures the list of failures
     */
    constructor(heading: String?, failures: List<Throwable>) : AssertionError {
    /**
     * Returns whether this error contains any failures.
     */
    fun hasFailures(): Boolean
}

/**
 * Returns the list of failures contained in this error.
 */
expect val MultipleFailuresError.failures: List<Throwable>

/**
 * [RuntimeException] used to indicate that the execution of a test
 * was incomplete &mdash; for example, that the execution was entirely
 * skipped or aborted mid-stream. See subclasses for concrete use cases.
 *
 * An `IncompleteExecutionException` is **not** used
 * to indicate that a test execution failed.
 *
 * @author Johannes Link
 * @author Sam Brannen
 * @since 1.0
 */
expect open class IncompleteExecutionException : RuntimeException {
    /**
     * Constructs an [IncompleteExecutionException] with an empty message and no cause.
     */
    constructor()
    /**
     * Constructs an [IncompleteExecutionException] with a message but without cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     */
    constructor(message: String?)
    /**
     * Constructs an [IncompleteExecutionException] with a message and a cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     * @param cause the cause of the failure
     */
    constructor(message: String?, cause: Throwable?)
}

/**
 * Specialization of [IncompleteExecutionException] used to indicate
 * that a test was aborted during execution (e.g., due to a failed
 * *assumption*).
 *
 * @author Sam Brannen
 * @author Johannes Link
 * @since 1.0
 * @see TestSkippedException
 */
expect open class TestAbortedException : IncompleteExecutionException {
    /**
     * Constructs an [TestAbortedException] with an empty message and no cause.
     */
    constructor()
    /**
     * Constructs an [TestAbortedException] with a message but without cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     */
    constructor(message: String?)
    /**
     * Constructs an [TestAbortedException] with a message and a cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     * @param cause the cause of the failure
     */
    constructor(message: String?, cause: Throwable?)
}

/**
 * Specialization of [IncompleteExecutionException] used to indicate
 * that a test was skipped *prior* to execution (e.g., *disabled*
 * or *ignored*).
 *
 * @author Sam Brannen
 * @author Johannes Link
 * @since 1.0
 * @see TestAbortedException
 */
expect open class TestSkippedException : IncompleteExecutionException {
    /**
     * Constructs an [TestSkippedException] with an empty message and no cause.
     */
    constructor()
    /**
     * Constructs an [TestSkippedException] with a message but without cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     */
    constructor(message: String?)
    /**
     * Constructs an [TestSkippedException] with a message and a cause.
     *
     * @param message the detail message; it is saved for later retrieval in the [message] property
     * @param cause the cause of the failure
     */
    constructor(message: String?, cause: Throwable?)
}

/**
 * `FileInfo` is a pair of a file path and the contents of the file.
 *
 * <p>Its main use case is for `actual` and `expected` values in
 * [AssertionFailedError].
 *
 * <p>The semantics of the `path` can be defined by the users of this
 * class.
 *
 * @author Marc Philipp
 * @author Reinhold Degenfellner
 * @since 1.3
 */
expect open class FileInfo
    /**
     * Constructs a `FileInfo` with a path to a file and the contents of
     * the file.
     *
     * <p>Be cautious with large files (limiting the size may be a good idea).
     *
     * <p>The supplied byte array is not copied to avoid unnecessary memory
     * allocations. However, callers of this constructors should not modify the
     * byte array after calling this constructor. This behavior may change
     * in a future release without prior notice.
     *
     * @param path the path to the file, must not be blank
     * @param contents the contents of the file
     */
    constructor(path: String, contents: ByteArray) {
}

/**
 * Returns the path to the file.
 *
 * @return the path to the file
 */
expect val FileInfo.path: String

/**
 * Returns the contents of the file.
 *
 * <p>This method does currently not return a defensive copy of the
 * contained byte array to avoid unnecessary memory allocations. However,
 * callers of this method should not modify the returned byte array. This
 * behavior may change in a future release without prior notice.
 *
 * @return the contents of the file
 */
expect val FileInfo.contents: ByteArray

/**
 * Returns the contents of the file as a `String`, assuming utf8
 *
 * @return the contents of the file as a `String`
 */
expect fun FileInfo.contentsAsString(): String

internal expect fun Throwable.className(): String
